package service.normalization;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.List;

import javax.annotation.Resource;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.CleanerTransformations;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.JDomSerializer;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.TagTransformation;
import org.springframework.stereotype.Service;
import org.w3c.tidy.Tidy;

import entities.Sheet;
import service.configuration.FieldType;
import service.configuration.IBlockConfig;
import service.configuration.IConfiguration;
import service.configuration.IFieldConfig;
import service.configuration.ISheetConfig;
import web.form.Block;
import web.form.Item;
import web.form.SheetView;

import org.jdom.*;


@Service
public class Normalizer implements INormalizer{
	
	@Resource
	IConfiguration config;
	
	public Normalizer() {
		
	}
	
	/*
	 * Parcours récursif de l'arbre JDom pour transformer et reconstruire certaines balises
	 * Balises autorisées : ul, ol, li, table, tbody, col, th, td, tr, a, span, b, i, u, p
	 */
	@SuppressWarnings("unchecked")
	public String recursiveTreeXMLCleaner(Element root){
		String sb = "";
		
		/*
		 * Récupération et parcours des attributs d'une balise
		 * Transformation des attributs de style en balise si besoin est
		 */
		List<Attribute> la = root.getAttributes();
		for(Attribute a : la){
			if(a.getName().toLowerCase().equals("bold") || a.getValue().toLowerCase().contains("bold"))
				root.setName("b");
			else if(a.getName().toLowerCase().equals("italic") || a.getValue().toLowerCase().contains("italic"))
				root.setName("i");
			else if(a.getName().toLowerCase().equals("underline") || a.getValue().toLowerCase().contains("underline"))
				root.setName("u");
		}
		//System.out.println("<"+root.getName()+">"+root.getText());
		
		/*
		 * Balises ouvrante
		 */
		if(root.getName().toLowerCase().equals("body")){
			sb = sb + "<p>";
		}
		if((!root.getText().equals("") && (root.getName().toLowerCase().equals("b")
				|| root.getName().toLowerCase().equals("i")
				|| root.getName().toLowerCase().equals("u")
				|| root.getName().toLowerCase().equals("col")
				|| root.getName().toLowerCase().equals("ul")
				|| root.getName().toLowerCase().equals("th")
				|| root.getName().toLowerCase().equals("td")
				|| root.getName().toLowerCase().equals("tr")
				|| root.getName().toLowerCase().equals("li")
				|| root.getName().toLowerCase().equals("p")
				)) || root.getText().equals("") && (root.getName().toLowerCase().equals("table")
						|| root.getName().toLowerCase().equals("ol")
						|| root.getName().toLowerCase().equals("ul")
						|| root.getName().toLowerCase().equals("tbody")
						|| root.getName().toLowerCase().equals("p")
						|| root.getName().toLowerCase().equals("col")
						|| root.getName().toLowerCase().equals("th")
						|| root.getName().toLowerCase().equals("td")
						|| root.getName().toLowerCase().equals("tr")
						|| root.getName().toLowerCase().equals("li")
						)){
			if(root.getName().toLowerCase().equals("table")
					|| root.getName().toLowerCase().equals("td")
					|| root.getName().toLowerCase().equals("tr")
					|| root.getName().toLowerCase().equals("th"))
				sb = sb + "<"+root.getName().toLowerCase() + " style=\"border: 1px solid #000000;\"" +">";
			else
				sb = sb + "<"+root.getName().toLowerCase()+">";
		}
		/* Récupération des fils Element et Text
		 * Parcours récursif des fils
		 */
		List<Content> elementsC = root.getContent();
		for(int i=0; i<elementsC.size();i++){
			//System.out.println(elementsC.get(i).toString());
			if(elementsC.get(i) instanceof Element){
				Element courant = (Element) elementsC.get(i);
				sb = sb + recursiveTreeXMLCleaner(courant);
			}else if(elementsC.get(i) instanceof Text){
				/*if(root.getName().equals("b")
						|| root.getName().equals("u")
						|| root.getName().equals("i"))
					sb = sb + elementsC.get(i).getValue();
				else 
					sb = sb + "<p><span>" + elementsC.get(i).getValue() + "</span></p>";*/
				sb = sb + elementsC.get(i).getValue();
			}
		}
		
		//System.out.println("</"+root.getName()+">");
		
		/*
		 * Balise fermante
		 */
		if((!root.getText().equals("") && (root.getName().toLowerCase().equals("b")
				|| root.getName().toLowerCase().equals("i")
				|| root.getName().toLowerCase().equals("u")
				|| root.getName().toLowerCase().equals("ul")
				|| root.getName().toLowerCase().equals("col")
				|| root.getName().toLowerCase().equals("th")
				|| root.getName().toLowerCase().equals("td")
				|| root.getName().toLowerCase().equals("tr")
				|| root.getName().toLowerCase().equals("li")
				|| root.getName().toLowerCase().equals("p")
				)) || root.getText().equals("") && (root.getName().toLowerCase().equals("table")
						|| root.getName().toLowerCase().equals("ol")
						|| root.getName().toLowerCase().equals("ul")
						|| root.getName().toLowerCase().equals("tbody")
						|| root.getName().toLowerCase().equals("p")
						|| root.getName().toLowerCase().equals("col")
						|| root.getName().toLowerCase().equals("th")
						|| root.getName().toLowerCase().equals("td")
						|| root.getName().toLowerCase().equals("tr")
						|| root.getName().toLowerCase().equals("li")
						))
			sb = sb + "</"+root.getName().toLowerCase()+">";
		if(root.getName().toLowerCase().equals("body")){
			sb = sb + "</p>";
		}
		return sb;
	}
	
	/**
	 * Normalise avec HTMLCleaner le texte reçu en paramètre, et le renvoie
	 * @param : String
	 * @return : String
	 */
	public String normalizeText(String textIN){

		if(textIN==null)
			return null;
		
		//System.out.println(textIN);
		String text = "";
		textIN = textIN.replace("\\n"," ");
		
		/* Fix d'une insertion de caractère caché */
		text = textIN.replace("\u200b","");
		
		//System.out.println(textIN);
		try{
			HtmlCleaner cleaner = new HtmlCleaner();
			Tidy tidy = new Tidy();
	 
			CleanerTransformations transformations = new CleanerTransformations();
		
		/* Récupération des propriétés du cleaner par défaut */
			CleanerProperties props = cleaner.getProperties();
		
		/* Mise en place des propriétés de HTMLCleaner */
			props.setOmitDoctypeDeclaration(true);
			props.setOmitHtmlEnvelope(false);
			props.setOmitXmlDeclaration(true);
			props.setPruneTags("style,form,script");
			
		/* Mise en place des transformations de tags */
			TagTransformation tt = new TagTransformation("font","span",true);
			transformations.addTransformation(tt);
			tt = new TagTransformation("div", "p");
			transformations.addTransformation(tt);
			tt = new TagTransformation("strong", "b");
			transformations.addTransformation(tt);
			tt = new TagTransformation("em", "i");
			transformations.addTransformation(tt);
			tt = new TagTransformation("p", "p", false);
			transformations.addTransformation(tt);
			for(int h=0; h<10; h++){
				tt = new TagTransformation("h"+h, "p");
				transformations.addTransformation(tt);
			}
			tt = new TagTransformation("br");
			transformations.addTransformation(tt);
			cleaner.setTransformations(transformations);
			
			TagNode node = cleaner.clean(text);
			
			//System.out.println(text);
			//System.err.println("HTML Cleaner " + new PrettyXmlSerializer(props).getAsString(node));
			
			/* Récupération d'un Document JDom */
			String sb = "";
			try{
				Document myJDom = new JDomSerializer(props, true).createJDom(node);
				sb = recursiveTreeXMLCleaner(myJDom.getRootElement());
			}catch(Exception e){
				e.printStackTrace();
			}
			
			//System.out.println("Fin recursif");
			//System.err.println("Recursive " + sb);
			
			/* Utilisation de JTidy */
			String s = "";
			try {
				InputStream is = new ByteArrayInputStream(sb.getBytes("utf-8"));
				OutputStream os = new ByteArrayOutputStream();
				
				/* Paramétrage de JTidy */
				tidy.setXHTML(true);
				tidy.setShowWarnings(false);
				tidy.setPrintBodyOnly(true);
				tidy.setQuoteMarks(false);
				tidy.setQuoteAmpersand(false);
				tidy.setQuoteNbsp(false);
				tidy.setDocType("strict");
				tidy.setInputEncoding("UTF-8");
				tidy.setOutputEncoding("UTF-8");
				tidy.setMakeClean(false);
				tidy.setEncloseBlockText(true);
				tidy.setEncloseText(true);
				tidy.setWrapAsp(true);
				tidy.setWrapJste(true);
				tidy.setAsciiChars(false);
				tidy.setDropFontTags(false);
				tidy.setTidyMark(false);
				tidy.setDropProprietaryAttributes(true);
				tidy.setTrimEmptyElements(true);
				tidy.setQuiet(true);
				//tidy.setErrout(null);
				
				/* Parse du texte en entrée par JTidy.
				 * Retourne en sortie un texte parsé.
				 */

				tidy.parse(is, os);

				s = os.toString().replace("\u0000", "");
				/* Suppression des BR générés par JTidy (remplacement des <p></p> en <br />) */

				s = s.replaceAll("\\s{2,}", " ");
				s = s.replaceAll("\\n+", " ");
				s = s.replaceAll("<*^>>\\s+</*^>>", " ");
				s = s.replaceAll(">\\s+<", "><");
				s = s.replaceAll("</*br\\s*/*>", "");
				
				//System.out.println(s);
			} catch (Exception e) {
				System.err.println("JTidy " + s);
				e.printStackTrace();
			}

			return s;
		} catch(Exception e){
			System.err.println("HTMLCleaner");
			e.fillInStackTrace();
			return null;
		}
	}
	
	/**
	 * [Synchronization] Recherche des champs d'une fiche qui doivent être normalisés par Normalizer,
	 * et renvoie la fiche avec les champs modifiés
	 * @param : Sheet
	 * @return : Sheet
	 */
	public Sheet normalizeSheet(Sheet sheet){
		
		ISheetConfig sc = config.getSheetConfig(sheet.getType());
		
		//System.out.println("Code fiche " + sheet.getCode());
		for(IBlockConfig bl : sc.getBlockConfigs()){
			for(IFieldConfig fi : bl.getFieldConfigs()){
				if(fi.getType() == FieldType.text){
					String key = fi.getName();
					String cleanedText = normalizeText((String) sheet.getProperty(key));
					sheet.setProperty(key, cleanedText);
				}
			}
		}
		return sheet;
	}
	
	/**
	 * [Web] Recherche des champs d'une fiche qui doivent être normalisés par Normalizer,
	 * et renvoie la fiche avec les champs modifiés
	 * @param : SheetView
	 * @return : SheetView
	 */
	public SheetView normalizeSheetView(SheetView sheet){
		
		for(Block block : sheet.getBlocks()){
			for(Item item : block.getItems()){
				if(item.getType() == FieldType.text){
					//System.out.println("Avant norm " + item.getValue().toString());
					item.setValue(normalizeText(item.getValue()));
					//System.out.println("Après norm " + item.getValue().toString());
				}
			}
		}
		return sheet;
	}

}
